package service

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"sort"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"

	"init-database/global"
	"init-database/model"
)

const (
	InitOrderSystem = 10
)

type SubInitializer interface {
	// InitializerName 返回表名
	InitializerName() string
	// MigrateTable 创建表
	MigrateTable(ctx context.Context) (context.Context, error)
	// TableCreated 判断表是否存在
	TableCreated(ctx context.Context) bool
	// InitializerData 向表中创建初始化数据
	InitializerData(ctx context.Context) (next context.Context, err error)
	// DataInserted 判断表中是否已存在数据
	DataInserted(ctx context.Context) bool
}

// orderedInitializer 组合一个顺序字段，以供排序
type orderedInitializer struct {
	order          int // 初始化顺序，用于排序
	SubInitializer     // SubInitializer 接口的实现，包含初始化逻辑
}

// initSlice 供 initializer 排序依赖时使用
type initSlice []*orderedInitializer

var (
	initializers initSlice
	cache        map[string]*orderedInitializer
)

// RegisterInit 注册初始化器，确保初始化器按顺序执行并且名称唯一
func RegisterInit(order int, i SubInitializer) {
	// 如果 initializers 切片未初始化，则初始化为空切片
	if initializers == nil {
		initializers = initSlice{}
	}
	// 如果 cache 映射未初始化，则初始化为空映射
	if cache == nil {
		cache = map[string]*orderedInitializer{}
	}
	// 获取初始化器的名称，用于唯一性检查
	name := i.InitializerName()
	// 检查名称是否已存在于 cache 中，防止重复注册
	if _, existed := cache[name]; existed {
		panic(fmt.Sprintf("Name conflict on %s", name)) // 抛出错误，提示名称冲突
	}
	// 创建一个 orderedInitializer 结构体实例，用于存储初始化器及其顺序
	ni := orderedInitializer{order, i}
	// 将 orderedInitializer 添加到 initializers 切片中
	initializers = append(initializers, &ni)
	// 将 orderedInitializer 存储到 cache 映射中，便于快速查找
	cache[name] = &ni
}

/* ---- * service * ---- */

// InitDBService 数据库初始化服务结构体
type InitDBService struct{}

// InitDB 初始化数据库服务方法
// @param conf model.InitDB 数据库配置信息
// @return error 返回初始化过程中的错误信息
func (i *InitDBService) InitDB(conf model.InitDB) (err error) {
	// 创建上下文
	ctx := context.TODO()
	if len(initializers) == 0 {
		return errors.New("无可用初始化过程，请检查初始化是否已执行完成")
	}
	sort.Sort(&initializers) // 保证有依赖的 initializer 排在后面执行

	// 设置数据库类型
	ctx = context.WithValue(ctx, "dbtype", "mysql")
	// 确保数据库连接
	if ctx, err = EnsureDB(ctx, &conf); err != nil {
		return err
	}
	// 获取数据库连接
	db := ctx.Value("db").(*gorm.DB)
	global.GLOBAL_DB = db

	if err = initTables(ctx, initializers); err != nil {
		return err
	}
	if err = initData(ctx, initializers); err != nil {
		return err
	}

	// 回写配置到配置文件
	if err = conf.WriteConfig(); err != nil {
		return fmt.Errorf("数据库配置回写失败：%v", err)
	}

	return nil
}

// EnsureDB 确保数据库连接可用
// @param ctx context.Context 上下文信息
// @param conf *model.InitDB 数据库配置信息
// @return next context.Context 包含数据库连接的上下文
// @return err error 返回连接过程中的错误信息
func EnsureDB(ctx context.Context, conf *model.InitDB) (next context.Context, err error) {
	if s, ok := ctx.Value("dbtype").(string); !ok || s != "mysql" {
		return ctx, errors.New("数据库类型不匹配，请检查配置")
	}
	// 转换配置
	c := conf.SetDefaults()
	fmt.Println(c)
	// 创建数据库连接字符串
	createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", conf.Database)
	// 创建数据库
	if err = createDatabase(c, "mysql", createSql); err != nil {
		return nil, err
	} // 创建数据库

	// 使用目标数据库连接
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s",
		conf.Username,
		conf.Password,
		conf.Host,
		conf.Port,
		conf.Database)

	// 打开数据库连接
	var db *gorm.DB
	if db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info), // 设置日志级别
	}); err != nil {
		return ctx, err
	}
	next = context.WithValue(ctx, "db", db)
	return next, err
}

// createDatabase 创建新的数据库
// @param dsn string 数据库连接字符串
// @param driver string 数据库驱动类型
// @param createSql string 创建数据库的SQL语句
// @return error 返回创建过程中的错误信息
func createDatabase(dsn, driver, createSql string) (err error) {
	db, err := sql.Open(driver, dsn)
	if err != nil {
		return err
	}
	defer func(db *sql.DB) {
		err = db.Close()
		if err != nil {
			fmt.Println("关闭数据库连接失败:", err)
		}
	}(db)
	if err = db.Ping(); err != nil {
		return err
	}
	_, err = db.Exec(createSql)
	return err
}

// initTables 初始化数据库表
// @param ctx context.Context 上下文信息
// @param inits initSlice 初始化器切片
// @return error 返回初始化过程中的错误信息
func initTables(ctx context.Context, inits initSlice) error {
	return createTables(ctx, inits)
}

// createTables 创建数据库表
// @param ctx context.Context 上下文信息
// @param inits initSlice 初始化器切片
// @return error 返回创建过程中的错误信息
func createTables(ctx context.Context, inits initSlice) error {
	next, cancel := context.WithCancel(ctx)
	defer func(c func()) { c() }(cancel)
	for _, init := range inits {
		if init.TableCreated(next) {
			continue
		}
		if n, err := init.MigrateTable(next); err != nil {
			return err
		} else {
			next = n
		}
	}
	return nil
}

// initData 初始化数据库表数据
// @param ctx context.Context 上下文信息
// @param inits initSlice 初始化器切片
// @return error 返回数据初始化过程中的错误信息
func initData(ctx context.Context, inits initSlice) error {
	next, cancel := context.WithCancel(ctx)
	defer func(c func()) { c() }(cancel)
	for _, init := range inits {
		if init.DataInserted(next) {
			continue
		}
		if n, err := init.InitializerData(next); err != nil {
			return err
		} else {
			next = n
		}
	}
	return nil
}

/* -- sortable interface -- */

// Len 返回切片长度
// @return int 返回初始化器切片的长度
func (a initSlice) Len() int {
	return len(a)
}

// Less 比较两个初始化器的顺序
// @param i int 第一个初始化器的索引
// @param j int 第二个初始化器的索引
// @return bool 如果i的顺序小于j则返回true
func (a initSlice) Less(i, j int) bool {
	return a[i].order < a[j].order
}

// Swap 交换两个初始化器的位置
// @param i int 第一个初始化器的索引
// @param j int 第二个初始化器的索引
func (a initSlice) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}
